package com.cgs.query.handler.render

import com.cgs.query.domain.*
import com.cgs.query.exception.QueryException
import com.cgs.query.executor.ExecutionContext
import com.cgs.query.handler.ParameterHandler
import com.cgs.query.util.EnumUtils
import freemarker.cache.NullCacheStorage
import freemarker.core.Environment
import freemarker.ext.util.WrapperTemplateModel
import freemarker.template.*
import freemarker.template.Configuration
import freemarker.template.utility.ObjectWrapperWithAPISupport
import java.io.Serializable
import java.io.StringReader
import java.io.StringWriter
import java.sql.JDBCType
import java.util.*
import freemarker.template.TemplateDirectiveBody
import freemarker.template.TemplateModel
import freemarker.template.TemplateModelException
import freemarker.template.DefaultObjectWrapper
import freemarker.template.TemplateDirectiveModel

/**
 * 使用freemarker引擎对sql脚本进行渲染
 */
class FreemarkerParameterHandler : ParameterHandler {

    companion object {
        val cfg = Configuration(Configuration.VERSION_2_3_23).apply {
            cacheStorage = NullCacheStorage.INSTANCE
        }
    }

    override fun resolve(executionObj: ExecutionObj, param: Map<String, Any?>): BoundSql {
        val reader = StringReader(executionObj.statement)
        val statementType = EnumUtils.valueOf(StatementType::class.java, executionObj.statementType)
        val parameterMappings = arrayListOf<ParameterMapping>()
        try {
            val sqlTemplate = SqlTemplate(parameterMappings)
            val template = Template("freemarker", reader, FreemarkerParameterHandler.cfg.apply { setSharedVariable("sql", sqlTemplate) })
            val stringWriter = StringWriter()
            when (statementType) {
                StatementType.STATEMENT, StatementType.PREPARED, StatementType.CALLABLE, StatementType.CALLABLE -> {
                    val sqlModel = SqlTemplateHashModel(param, parameterMappings)
                    template.process(sqlModel, stringWriter)
                    return BoundSql(stringWriter.toString(), parameterMappings)
                }
                else -> throw QueryException("Freemarker不支持该查询方式的渲染")
            }
        } catch (e: Exception) {
            throw QueryException("模板渲染：失败 ", e)
        }
    }

    override fun resolve(executionObj: ExecutionObj, param: Map<String, Any?>, executionContext: ExecutionContext): BoundSql {
        return resolve(executionObj, param + mapOf("dbType" to executionContext.dbType))
    }
}

/**
 * sql渲染模型
 */
class SqlTemplateHashModel(
        private val map: Map<*, *>,
        private val parameterMappings: ArrayList<ParameterMapping>
) : WrappingTemplateModel(SqlTemplateHashModel.wrapper), TemplateHashModelEx, AdapterTemplateModel, WrapperTemplateModel, TemplateModelWithAPISupport, Serializable {

    @Throws(TemplateModelException::class)
    override fun get(key: String): TemplateModel? {
        var key = key
        var isParam = false
        if (key.endsWith("_")) {
            isParam = true
            key = key.substring(0, key.length - 1)
        }
        val parts = key.split("_")
        if (parts.size == 3) {
            isParam = true
            key = parts[0]
        }
        var value: Any?
        try {
            value = map[key]
        } catch (e: ClassCastException) {
            throw QueryException("变量类型转换失败：$key")
        } catch (e: NullPointerException) {
            throw QueryException("缺少变量：$key")
        }

        if (value == null) {
            // Check for Character key if this is a single-character string.
            // In SortedMap-s, however, we can't do that safely, as it can cause ClassCastException.
            if (key.length == 1 && map !is SortedMap<*, *>) {
                val charKey = key[0]
                try {
                    value = map[charKey]
                    if (value == null) {
                        val wrappedNull = wrap(null)
                        return if (wrappedNull == null || !(map.containsKey(key) || map.containsKey(charKey))) {
                            null
                        } else {
                            wrappedNull
                        }
                    }
                } catch (e: ClassCastException) {
                    throw QueryException("变量类型转换失败：$charKey")
                } catch (e: NullPointerException) {
                    throw QueryException("缺少变量：$charKey")
                }

            } else {  // No char key fallback was possible
                val wrappedNull = wrap(null)
                return if (wrappedNull == null || !map.containsKey(key)) {
                    null
                } else {
                    wrappedNull
                }
            }
        }
        if (isParam) {
            if (parts.size == 3) {
                val paramMode = EnumUtils.valueOf(ParameterMode::class.java, parts[1])
                val paramJdbcType = EnumUtils.valueOf(JDBCType::class.java, parts[2])
                this.parameterMappings.add(ParameterMapping(key, value).apply {
                    mode = paramMode
                    jdbcType = paramJdbcType
                })
            } else {
                this.parameterMappings.add(ParameterMapping(key, value))
            }
            return wrap("?")
        }
        return wrap(value)
    }

    override fun isEmpty(): Boolean {
        return map.isEmpty()
    }

    override fun size(): Int {
        return map.size
    }

    override fun keys(): TemplateCollectionModel {
        return SimpleCollection(map.keys, objectWrapper)
    }

    override fun values(): TemplateCollectionModel {
        return SimpleCollection(map.values, objectWrapper)
    }

    override fun getAdaptedObject(hint: Class<*>): Any {
        return map
    }

    override fun getWrappedObject(): Any {
        return map
    }

    @Throws(TemplateModelException::class)
    override fun getAPI(): TemplateModel {
        return (objectWrapper as ObjectWrapperWithAPISupport).wrapAsAPI(map)
    }

    companion object {
        private val wrapper = DefaultObjectWrapper(Configuration.VERSION_2_3_23)
    }

}

/**
 * <@sql x ='name,model,type'/>
 */
class SqlTemplate(private val parameterMappings: ArrayList<ParameterMapping>) : TemplateDirectiveModel {

    override fun execute(env: Environment?, params: MutableMap<Any?, Any?>?, result: Array<out TemplateModel>?, body: TemplateDirectiveBody?) {

        val temp = if (params?.isEmpty() == true) {
            body.toString()
        } else {
            params!!.entries.first().value.toString()
        }

        if (temp.isNotEmpty()) {
            val parts = temp.split(",")
            var paramMode: ParameterMode? = null
            var paramJdbcType: JDBCType? = null
            when (parts.size) {
                1 -> {
                }
                2 -> paramMode = EnumUtils.valueOf(ParameterMode::class.java, parts[1])
                3 -> {
                    paramMode = EnumUtils.valueOf(ParameterMode::class.java, parts[1])
                    paramJdbcType = EnumUtils.valueOf(JDBCType::class.java, parts[2])
                }
                else -> null
            } ?: throw QueryException("格式错误：<@sql x ='name,model,type'/>")

            this.parameterMappings.add(ParameterMapping(parts[0], env!!.dataModel[parts[0]]).apply {
                notNullThen(paramMode) { mode = paramMode!! }
                notNullThen(paramJdbcType) { jdbcType = paramJdbcType!! }

            })
            env.out.write("?")
            return
        }
        throw QueryException("格式错误：<@sql x ='name,model,type'/>")
    }
}

fun notNullThen(p: Any?, block: () -> Unit) {
    if(p !=null) block()
}